/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.system.application.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;

import com.foreveross.bsl.auth.AuthAccount;
import com.foreveross.bsl.auth.AuthUser;
import com.foreveross.bsl.auth.service.TicketApplication;
import com.foreveross.bsl.common.utils.bean.BeanHelper;
import com.foreveross.bsl.common.utils.context.ContextThead;
import com.foreveross.bsl.common.utils.enums.AccountStatus;
import com.foreveross.bsl.common.utils.enums.AuthType;
import com.foreveross.bsl.common.utils.enums.OperatEnum;
import com.foreveross.bsl.common.utils.enums.Protocol;
import com.foreveross.bsl.common.utils.exception.AppExcType;
import com.foreveross.bsl.common.utils.exception.ApplicationException;
import com.foreveross.bsl.mam.model.rbac.domain.Admin;
import com.foreveross.bsl.mam.model.rbac.domain.User;
import com.foreveross.bsl.system.application.AccountApplication;
import com.foreveross.bsl.system.application.extend.AuthSettingApplication;
import com.foreveross.bsl.system.application.extend.AuthTypeApplication;
import com.foreveross.bsl.system.application.extend.vo.AuthInfoVO;
import com.foreveross.bsl.system.application.extend.vo.AuthResultVO;
import com.foreveross.bsl.system.application.extend.vo.AuthSettingVO;
import com.foreveross.bsl.system.application.impl.extend.AuthInstanceFactory;
import com.foreveross.bsl.system.application.vo.AccountVO;
import com.foreveross.bsl.system.model.domain.Account;
import com.foreveross.bsl.system.model.domain.ApplicationRegister;
import com.foreveross.bsl.system.model.domain.DatabaseInfo;
import com.foreveross.bsl.system.model.domain.RouterDatabase;
import com.mongodb.WriteResult;

/**
 * @description
 * 
 * @author rening
 * @version v1.0
 * 
 * @2013-6-7
 * 
 */
@Named("accountApplication")
public class AccountApplicationImpl implements AccountApplication {

	@Inject
	TicketApplication ticketApplication;

	@Inject
	AuthSettingApplication authSettingApplication;
	
    @Inject MongoTemplate mongoTemplate;
    
    
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#saveEntity
	 * (com.foreveross.bsl.system.application.vo.DatabaseInfoVO)
	 */
	@Override
	public AccountVO saveEntity(AccountVO entity) {
		Account account = new Account();
		BeanHelper.copyProperties(account, entity);
		if (account.getStatus() == null) {
			account.setStatus("a");
		}
		account.setCreateDate(new Date());
		account.save();
		entity.setId(account.getId());

		DatabaseInfo db = new DatabaseInfo(); // 程序启动时，会扫描本地mongodb实体映射此集合
		Iterable rs = db.findAll();
		if (rs != null) {
			RouterDatabase r; 
			for (Iterator iterator = rs.iterator(); iterator.hasNext();) {
				// 设置主库信息到各个业务分库（可能不同机器的mongodb）
				DatabaseInfo obj = (DatabaseInfo) iterator.next();
				r = new RouterDatabase();
				r.setConnName(obj.getConnName());
				r.setDatabaseName(entity.getUsername()); // 根据租户名称设置业务库名称
				r.setRegisterNo(entity.getUsername()); // 设置业务库注册号
				r.save();
			}
		}
		return entity;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#updateEntity
	 * (com.foreveross.bsl.system.application.vo.DatabaseInfoVO)
	 */
	@Override
	public AccountVO updateEntity(String id, AccountVO entity) {
		Account account = new Account();
		account = (Account) account.findOne(id);
		BeanHelper.copyProperties(account, entity);
		account.save();
		BeanHelper.copyProperties(entity, account);
		return entity;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#deleteEntity
	 * (com.foreveross.bsl.system.application.vo.DatabaseInfoVO)
	 */
	@Override
	public void deleteEntity(String id) {
		Account account = new Account();
		account = (Account) account.findOne(id);
		account.delete();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#getEntity
	 * (java.lang.String)
	 */
	@Override
	public AccountVO getEntity(String id) {
		Account account = new Account();
		account = (Account) account.findOne(id);
		AccountVO vo = new AccountVO();
		BeanHelper.copyProperties(vo, account);
		return vo;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#getEntity
	 * (java.lang.String)
	 */
	@Override
	public List<AccountVO> getEntityByUserName(String username) {
		Account account = new Account();
		OperatEnum operat = OperatEnum.EQ;
		String proName = "username";
		List<Account> accounts = (List<Account>) account.findListByProperty(
				proName, operat, username);
		if (accounts != null) {
			List<AccountVO> result = new ArrayList<AccountVO>();
			AccountVO vo;
			for (Iterator iterator = accounts.iterator(); iterator.hasNext();) {
				Account obj = (Account) iterator.next();

				vo = new AccountVO();
				BeanHelper.copyProperties(vo, obj);
				result.add(vo);
			}
			return result;
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#getEntity
	 * (java.lang.String)
	 */
	@Override
	public AccountVO getEntityByUserNameAndPassword(String username,
			String password) {
		Account account = new Account();
		OperatEnum[] operats = new OperatEnum[] { OperatEnum.EQ, OperatEnum.EQ };
		String proName1 = "username";
		String proName2 = "password";
		String[] proName = new String[] { proName1, proName2 };

		Md5PasswordEncoder md = new Md5PasswordEncoder();
		String encodePassword = md.encodePassword(password, username);
		String[] values = new String[] { username, encodePassword };
		List<Account> accounts = (List<Account>) account.findListByProperty(
				proName, operats, values);
		if (accounts != null && accounts.size() > 0) {
			AccountVO vo = new AccountVO();
			BeanHelper.copyProperties(vo, accounts.get(0));
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#findAll()
	 */
	@Override
	public List<AccountVO> findAll() {
		Account account = new Account();
		Iterable iterable = account.findAll();
		if (iterable != null) {
			List<AccountVO> result = new ArrayList<AccountVO>();
			AccountVO vo;
			for (Iterator iterator = iterable.iterator(); iterator.hasNext();) {
				Account obj = (Account) iterator.next();

				vo = new AccountVO();
				BeanHelper.copyProperties(vo, obj);
				result.add(vo);
			}
			return result;
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.DatabaseInfoApplication#findByPageable
	 * (org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<AccountVO> findByPageable(Pageable pageable) {
		Account account = new Account();
		Page page = account.findAll(pageable);
		if (page != null && page.getContent() != null) {
			List<AccountVO> result = new ArrayList<AccountVO>();
			AccountVO vo;
			for (Iterator iterator = page.getContent().iterator(); iterator
					.hasNext();) {
				Account obj = (Account) iterator.next();

				vo = new AccountVO();
				BeanHelper.copyProperties(vo, obj);
				result.add(vo);
			}
			Page<AccountVO> pageVO = new PageImpl<AccountVO>(result, pageable,
					page.getTotalElements());
			return pageVO;
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.AccountApplication#findByPageable
	 * (org.springframework.data.domain.Pageable,
	 * com.foreveross.bsl.system.application.vo.AccountVO)
	 */
	@Override
	public Page<AccountVO> findByPageable(Pageable pageable, AccountVO account) {
		Account acc = new Account();
		BeanHelper.copyProperties(acc, account);
		Page page = acc.findByPageable(pageable, acc);
		if (page != null && page.getContent() != null) {
			List<AccountVO> result = new ArrayList<AccountVO>();
			AccountVO vo;
			for (Iterator iterator = page.getContent().iterator(); iterator
					.hasNext();) {
				Account obj = (Account) iterator.next();

				vo = new AccountVO();
				BeanHelper.copyProperties(vo, obj);
				result.add(vo);
			}
			Page<AccountVO> pageVO = new PageImpl<AccountVO>(result, pageable,
					page.getTotalElements());
			return pageVO;
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.AccountApplication#login(java.lang
	 * .String, java.lang.String)
	 */
	@Override
	public String login(String username, String password) {
		Md5PasswordEncoder md = new Md5PasswordEncoder();
		String encodePassword = md.encodePassword(password, username);

		Account acc = new Account();
		List<Account> list = acc.findListByProperty(new String[] { "username",
				"password" },
				new OperatEnum[] { OperatEnum.EQ, OperatEnum.EQ },
				new String[] { username, encodePassword });
		if (list != null && !list.isEmpty()) {
			acc = list.get(0);
			if ("p".equals(acc.getStatus())) {
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "021"),
						"Account is suspended! ");
			} else if ("d".equals(acc.getStatus())) {
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "022"),
						"Account is disabled! ");
			} else if ("i".equals(acc.getStatus())) {
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "023"),
						"Account is inactive! ");
			} else if ("a".equals(acc.getStatus())) {
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "024"),
						"Account is auditing! ");
			}
			AuthAccount aa = new AuthAccount();
			BeanHelper.copyProperties(aa, acc);
			String sessionKey = ticketApplication.generateTicket(aa);
			return sessionKey;
		} else {
			throw new ApplicationException(AppExcType.getBusCode(
					AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "025"),
					"Account is not exist or Wrong password! ");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.AccountApplication#mobileLogin(
	 * java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public String mobileLogin(String appKey, String username, String password) {
		Md5PasswordEncoder md = new Md5PasswordEncoder();
		String encodePassword = md.encodePassword(password, username);

		ApplicationRegister ar = new ApplicationRegister();
		List<ApplicationRegister> appList = ar.findListByProperty("appKey",
				OperatEnum.EQ, appKey);
		if (appList == null || appList.isEmpty()) {
			throw new ApplicationException("",
					"application is not register or Wrong appKey! ");
		}

		Account acc = new Account();
		acc = (Account) acc.findOne(appList.get(0).getAccountId());
		if (acc == null) {
			throw new ApplicationException("",
					"application is not register or Wrong appKey! ");
		}
		ContextThead.setThreadVar(acc.getUsername());
		User user = new User();
		List<User> list = user.findListByProperty(new String[] { "username",
				"password" },
				new OperatEnum[] { OperatEnum.EQ, OperatEnum.EQ },
				new String[] { username, encodePassword });
		if (list != null && !list.isEmpty()) {
			user = list.get(0);
			if ("p".equals(user.getStatus())) {
				throw new ApplicationException("", "Account is suspended! ");
			} else if ("d".equals(user.getStatus())) {
				throw new ApplicationException("", "Account is disabled! ");
			} else if ("i".equals(user.getStatus())) {
				throw new ApplicationException("", "Account is inactive! ");
			} else if ("a".equals(user.getStatus())) {
				throw new ApplicationException("", "Account is auditing! ");
			}
			AuthUser authUser = new AuthUser();
			BeanHelper.copyProperties(authUser, user);
			authUser.setAccountId(acc.getId());
			authUser.setAccountName(acc.getName());
			authUser.setAccountUsername(acc.getUsername());
			String sessionKey = ticketApplication.generateTicket(authUser);
			return sessionKey;
		} else {
			throw new ApplicationException("",
					"Username is not exist or Wrong password! ");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.AccountApplication#mobileLogin(
	 * java.lang.String)
	 */
	@Override
	public void logout(String sessionKey) {
		ticketApplication.deleteEntity(sessionKey);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.foreveross.bsl.system.application.AccountApplication#login(java.lang
	 * .String, java.lang.String, java.lang.String)
	 */
	@Override
	public String login(String accUsername, String username, String password) {
		Account acc = new Account();
		List<Account> list = acc.findListByProperty(
				new String[] { "username" },
				new OperatEnum[] { OperatEnum.EQ },
				new String[] { accUsername });
		if (!list.isEmpty()) {
			acc = list.get(0);
			AccountStatus as = AccountStatus.getByCode(acc.getStatus());
			switch (as) {
			case Suspended:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "021"),
						"Account is suspended! ");
			case Disabled:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "022"),
						"Account is disabled! ");
			case Inactive:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "023"),
						"Account is inactive! ");
			case Auditing:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "024"),
						"Account is auditing! ");
			default:
				break;
			}

		} else {
			throw new ApplicationException(AppExcType.getBusCode(
					AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "025"),
					"Account is not exist or Wrong password! ");
		}
		// current User auth type
		String code = acc.getAdminAuthType() != null ? acc.getAdminAuthType() : AuthType.Default.getValue();
		AuthType authType = AuthType.getByCode(code);

		Admin admin = null;
		AuthResultVO authResult = null;
		switch (authType) {
		case External:
			authResult = extendAuth(username, password, acc);
			admin = new Admin();
			admin.setUsername(username);
			admin.setStatus(AccountStatus.Normal.getValue());
			break;
		default:
			ContextThead.setThreadVar(acc.getUsername()); // 登录时把租户名放到上下文？如何保证每次客户端请求都是同一个线程？除非只有一个用户...
			admin = systemAuth(username, password);
//			admin = new Admin();
//			admin.setUsername(username);
//			admin.setStatus(AccountStatus.Normal.getValue());
			break;
		}
		AuthAccount authAcc = new AuthAccount();
		BeanHelper.copyProperties(authAcc, admin);
		authAcc.setAccountId(acc.getId());
		authAcc.setAccountName(acc.getName());
		authAcc.setAccountUsername(acc.getUsername());
		authAcc.setAccountStatus(acc.getStatus());
		authAcc.setAuthType(acc.getAuthType());
		authAcc.setAdminAuthType(acc.getAdminAuthType());
		authAcc.setAccountType(acc.getAccountType());
		String sessionKey = this.ticketApplication.generateTicket(authAcc);
		return sessionKey;
	}

	/**
	 * @param username
	 * @param password
	 * @param acc
	 * @param user
	 * @return
	 */
	private AuthResultVO extendAuth(String username, String password,
			Account acc) {
		AuthInfoVO authInfo = new AuthInfoVO();
		authInfo.setUsername(username);
		authInfo.setPassword(password);

		AuthSettingVO authSetting = null;
		List<AuthSettingVO> authSettingList = authSettingApplication
				.getEntityByUserName(acc.getUsername());
		if (!authSettingList.isEmpty()) {
			for (AuthSettingVO authSettingVO : authSettingList) {
				if ("b".equals(authSettingVO.getClientType())) {
					authSetting = authSettingVO;
					break;
				}
			}
		}

		if (authSetting == null) {
			throw new ApplicationException(AppExcType.getBusCode(
					AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "031"),
					"auth setting not set ");
		}

		Protocol p = Protocol.getByCode(authSetting.getProtocol());
		AuthTypeApplication authTypeApp = AuthInstanceFactory
				.getAuthInstance(p);
		AuthResultVO authResult = authTypeApp.auth(authInfo, authSetting);

		Admin admin = null;
		if (authResult.isResult()) {
			admin = new Admin();
			admin.setUsername(username);
			admin.setStatus(AccountStatus.Normal.getValue());

			ContextThead.setThreadVar(acc.getUsername());
			List<Admin> list = null;
			list = admin.findListByProperty(new String[] { "username" },
					new OperatEnum[] { OperatEnum.EQ },
					new String[] { username });
			if (list.isEmpty()) {
				admin.save();
			}
		} else {
			throw new ApplicationException(AppExcType.getBusCode(
					AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "007"),
					"Username is not exist or Wrong password! ");
		}

		return authResult;
	}

	/**
	 * 还没拆分完成，暂时注释掉
	 * 
	 * @param username
	 * @param result
	 * @param encodePassword
	 * @param acc
	 * @return
	 * 
	 */
	private Admin systemAuth(String username, String password) {

		Md5PasswordEncoder md = new Md5PasswordEncoder();
		String encodePassword = md.encodePassword(password, username);

		Admin admin = new Admin();
		List<Admin> list = null;
		list = admin.findListByProperty(
				new String[] { "username", "password" }, new OperatEnum[] {
						OperatEnum.EQ, OperatEnum.EQ }, new String[] {
						username, encodePassword });
		if (!list.isEmpty()) {

			admin = list.get(0);
			AccountStatus as = AccountStatus.getByCode(admin.getStatus());
			switch (as) {
			case Suspended:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "003"),
						"user is suspended! ");
			case Disabled:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "004"),
						"user is disabled! ");
			case Inactive:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "005"),
						"user is inactive! ");
			case Auditing:
				throw new ApplicationException(AppExcType.getBusCode(
						AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "006"),
						"user is auditing! ");
			default:
				break;
			}

			return admin;
		} else {
			throw new ApplicationException(AppExcType.getBusCode(
					AppExcType.APP_LAYER, AppExcType.SYS_MODUL, "007"),
					"Username is not exist or Wrong password! ");
		}
	}

	@Override
	public List<AccountVO> findAllByStatus(String status) {
		Account account = new Account();
		//Iterable iterable = account.findListByProperty("status", OperatEnum.EQ, status);
		Iterable iterable =account.findListByQuery(Query.query(Criteria.where("status").is(status)).with(new Sort(Direction.DESC,"create_date")));
		if (iterable != null) {
			List<AccountVO> result = new ArrayList<AccountVO>();
			AccountVO vo;
			for (Iterator iterator = iterable.iterator(); iterator.hasNext();) {
				Account obj = (Account) iterator.next();

				vo = new AccountVO();
				BeanHelper.copyProperties(vo, obj);
				result.add(vo);
			}
			return result;
		}
		return null;
	}

	@Override
	public int updateMultiStatusByIds(String[] ids,String status) {
		Query query = Query.query(Criteria.where("id").in(ids));
		Update update=Update.update("status", status).set("lastErrorMsg", "AccountApplication updateMultiStatusByIds fail with ids:"+Arrays.toString(ids));
		WriteResult result=mongoTemplate.updateMulti(query, update, Account.class);
		return result.getN();
	}

}
